package away3d.cameras ;


import away3d.cameras.lenses.LensBase ;
import away3d.cameras.lenses.PerspectiveLens ;
import away3d.core.math.Matrix3DUtils ;
import away3d.core.math.Plane3D ;
import away3d.core.partition.CameraNode ;
import away3d.core.partition.EntityNode ;
import away3d.entities.Entity ;
import flash.geom.Matrix3D ;
import flash.geom.Vector3D ;



/**
 * A Camera3D object represents a virtual camera through which we view the scene.
 */
class Camera3D extends Entity
{
    
    
    private var _viewProjectionInvalid  : Bool ;
    private var _viewProjection         : Matrix3D ;
    private var _lens                   : LensBase ;
    private var _frustumPlanes          : Vector<Plane3D> ;
    private var _frustumPlanesInvalid   : Bool ;
    
    
    
    /**
     * Creates a new Camera3D object
     * @param lens An optional lens object that will perform the projection. Defaults to PerspectiveLens.
     *
     * @see away3d.cameras.lenses.PerspectiveLens
     */
    public function new( ?lens : LensBase = null )
    {
        
        super() ;
        
        _viewProjection             = new Matrix3D() ;
        _viewProjectionInvalid      = true ;
        _frustumPlanesInvalid       = true ;
        _lens                       = lens || new PerspectiveLens() ;
        _lens.onInvalidateMatrix    = onInvalidateLensMatrix ;
        _frustumPlanes              = new Vector( 6, true ) ;
        
        for ( i in 0...6 )
        {
             
             _frustumPlanes[ i ] = new Plane3D() ;
             
        }
        
        z = -500 ;
        
    }
    
    
    private function get_frustumPlanes() : Vector<Plane3D>
    {
        
        if( _frustumPlanesInvalid ) updateFrustumPlanes() ;
        return _frustumPlanes ;
        
    }
    
    
    public function unproject( mX : Float, mY : Float ):Vector3D<Float>
    {
        
        return sceneTransform.transformVector( lens.unproject( mX, mY, 0 ) ) ;
        
    }
    
    
    public var lens( get_lens, set_lens ) : LensBase ;
    
    
    /**
     * The lens used by the camera to perform the projection ;
     */
    private function get_lens() : LensBase
    {
        
        return _lens ;
        
    }
    
    
    private function set_lens( value : LensBase )
    {
        
        if( _lens == value ) return value ;
        
        if( !value ) throw new Error( "Lens cannot be null!" ) ;
        
        _lens.onInvalidateMatrix    = null ;
        _lens                       = value ;
        _lens.onInvalidateMatrix    = onInvalidateLensMatrix ;
        
        return value ;
        
    }
    
    
    public var viewProjection( get_viewProjection, null ) : Matrix3D ;
    
    
    /**
     * The view projection matrix of the camera.
     */
    private function get_viewProjection() : Matrix3D
    {
        
        if( _viewProjectionInvalid )
        {
            
            _viewProjection.copyFrom( inverseSceneTransform ) ;
            _viewProjection.append( _lens.matrix ) ;
            _viewProjectionInvalid = false ;
            
        }
        
        return _viewProjection ;
        
    }
    
    
    private function updateFrustumPlanes()
    {
        
        var c11 : Float ;
        var c12 : Float ;
        var c13 : Float ;
        var c14 : Float ;
        var c21 : Float ;
        var c22 : Float ;
        var c23 : Float ;
        var c24 : Float ;
        var c31 : Float ;
        var c32 : Float ;
        var c33 : Float ;
        var c34 : Float ;
        var c41 : Float ;
        var c42 : Float ;
        var c43 : Float ;
        var c44 : Float ;
        var p   : Plane3D ;
        var raw = Matrix3DUtils.RAW_DATA_CONTAINER ;

        viewProjection.copyRawDataTo( raw ) ;

        c11 = raw[ 0  ] ;
        c12 = raw[ 4  ] ;
        c13 = raw[ 8  ] ;
        c14 = raw[ 12 ] ;
        c21 = raw[ 1  ] ;
        c22 = raw[ 5  ] ;
        c23 = raw[ 9  ] ;
        c24 = raw[ 13 ] ;
        c31 = raw[ 2  ] ;
        c32 = raw[ 6  ] ;
        c33 = raw[ 10 ] ;
        c34 = raw[ 14 ] ;
        c41 = raw[ 3  ] ;
        c42 = raw[ 7  ] ;
        c43 = raw[ 11 ] ;
        c44 = raw[ 15 ] ;
        
        // left plane
        p   = _frustumPlanes[ 0 ] ;
        p.a = c41 + c11 ;
        p.b = c42 + c12 ;
        p.c = c43 + c13 ;
        p.d = c44 + c14 ;
        
        // right plane
        p   = _frustumPlanes[ 1 ] ;
        p.a = c41 - c11 ;
        p.b = c42 - c12 ;
        p.c = c43 - c13 ;
        p.d = c44 - c14 ;
        
        // bottom
        p   = _frustumPlanes[ 2 ] ;
        p.a = c41 + c21 ;
        p.b = c42 + c22 ;
        p.c = c43 + c23 ;
        p.d = c44 + c24 ;
        
        // top
        p   = _frustumPlanes[ 3 ] ;
        p.a = c41 - c21 ;
        p.b = c42 - c22 ;
        p.c = c43 - c23 ;
        p.d = c44 - c24 ;
        
        // near
        p   = _frustumPlanes[ 4 ] ;
        p.a = c31 ;
        p.b = c32 ;
        p.c = c33 ;
        p.d = c34 ;
        
        // far
        p   = _frustumPlanes[ 5 ] ;
        p.a = c41 - c31 ;
        p.b = c42 - c32 ;
        p.c = c43 - c33 ;
        p.d = c44 - c34 ;
        
        _frustumPlanesInvalid = false ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override private function invalidateSceneTransform()
    {
        
        super.invalidateSceneTransform() ;
        _viewProjectionInvalid  = true ;
        _frustumPlanesInvalid   = true ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override private function updateBounds()
    {
        
        _bounds.nullify() ;
        _boundsInvalid = false ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override private  function createEntityPartitionNode() : EntityNode
    {
        
        return new CameraNode( this ) ;
        
    }
    
    
    private function onInvalidateLensMatrix()
    {
        
        _viewProjectionInvalid  = true ;
        _frustumPlanesInvalid   = true ;
        
    }
    
    
}